#include "mfcore.hpp"
#include "SDL2/SDL_image.h"

bool operator!=(SDL_Color& l,SDL_Color& r){
    return memcmp(&l,&r,sizeof(SDL_Color));
}
bool operator==(SDL_Color& l,SDL_Color& r){
    return !memcmp(&l,&r,sizeof(SDL_Color));
}

unordered_map<Uint32,Window*>* windows;
SDL_Renderer* gRender;

void Window::RemoveTask(Task* t){
    auto f=tasks.find(t);
    if(f!=tasks.end()){
        tasks.erase(f);
        delete t;
    }
}

Task* Window::AddTask(void(*func)(void*),void* arg,unsigned int max_frame,
bool every,bool forever,void(*finish)(void*),void* finish_arg){
    auto temp=new Task{func,finish,0,max_frame,arg,finish_arg,every,forever};
    tasks.emplace(temp);
    return temp;
}
SDL_Window* Window::GetSDLWindow(){return window;}

void Window::CancelFocus(Control* c){
    auto f=focus.find(c);
    if(f!=focus.end()){
        focus.erase(f);
        c->LoseFocus();
    }
}
void Window::AddFocus(Control* c){
    if(focus.emplace(c).second){
        c->OnFocus();
    }
}

Window::~Window(){
    delete renderer;
    if(parent){
        parent->children.erase(parent->children.find(this));
    }
    windows->erase(windows->find(_id));
    SDL_DestroyWindow(window);
    for(auto& item:controls)delete item;
    for(auto& item:children){
        item->parent=0;
        delete item;
    }
    for(auto& item:tasks)delete item;
}

Control* Window::GetCheck(int x,int y){
    Control* temp=0;
    for(auto& n:controls){
        if(!n->enable())continue;
        auto& r=n->area();
        if(r.x<=x&&x<=r.x+r.w&&r.y<=y&&y<=r.y+r.h){
            if(temp){
                if(temp->layer>n->layer)continue;
            }
            temp=n;
        }
    }
    return temp;
}

void Window::CheckMouseDown(int x,int y,int clicks,unsigned char key){
    if(!_mouse_press(x,y,clicks,key)){
        return;
    }
    _last_click=GetCheck(x,y);
    if(_last_click){
        _last_click->pressed=true;
        _last_click->_press(x,y,clicks,key);
    }
}

void Window::CheckMouseUp(int x,int y,int clicks,unsigned char key){
    if(!_mouse_release(x,y,clicks,key)){
        return;
    }
    auto back=GetCheck(x,y);
    if(back==_last_click&&back){
        auto f=focus.find(back);
        if(f!=focus.end())focus.erase(f);else back->OnFocus();
        auto item=focus.begin(),end=focus.end();
        while(item!=end){
            auto cur=item++;
            (*cur)->LoseFocus();
        }
        focus.clear();
        back->pressed=false;
        back->_release(x,y,clicks,key);
        focus.emplace(back);
        return;
    }
    if(_last_click){
        _last_click->pressed=false;
    }
    auto item=focus.begin(),end=focus.end();
    while(item!=end){
        auto cur=item++;
        (*cur)->LoseFocus();
    }
    focus.clear();
}

void Window::CheckMouseWheel(SDL_MouseWheelEvent& event){
    int x,y;
    SDL_GetMouseState(&x,&y);
    auto back=GetCheck(x,y);
    if(back){
        back->_mouse_wheel(event);
    }
}

void Window::CheckMouseMove(int x,int y){
    if(!_mouse_move(x,y)){
        return;
    }
    auto back=GetCheck(x,y);
    if(back){
        back->_mouse_move(x,y);
    }
}

void Window::CheckTextInput(const char* text){
    auto item=focus.begin(),end=focus.end();
    while(item!=end){
        auto cur=item++;
        (*cur)->_input(text);
    }
}

void Window::CheckKeyDown(SDL_Keysym& sym){
    if(!_key_down(sym)){
        return;
    }
    auto item=focus.begin(),end=focus.end();
    while(item!=end){
        auto cur=item++;
        (*cur)->_key_down(sym);
    }
}

void Window::CheckKeyUp(SDL_Keysym& sym){
    if(!_key_up(sym)){
        return;
    }
    auto item=focus.begin(),end=focus.end();
    while(item!=end){
        auto cur=item++;
        (*cur)->_key_up(sym);
    }
}

Window::Window(const char* title,int w,int h,bool resize,bool full,SDL_Color back,Window* parent,SDL_BlendMode mode){
    not_top=false;
    background=back;
    Uint32 flag=SDL_WINDOW_ALLOW_HIGHDPI|SDL_WINDOW_OPENGL;
    if(resize){
        flag|=SDL_WINDOW_RESIZABLE;
    }
    if(full){
        flag|=SDL_WINDOW_FULLSCREEN;
    }
    window=SDL_CreateWindow(title,SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,w,h,flag);
    _id=SDL_GetWindowID(window);
    renderer=new Renderer(window,mode);
    windows->insert(make_pair(_id,this));
    this->parent=parent;
    if(parent){
        parent->children.insert(this);
        renderer=parent->renderer;
    }
}

SDL_Texture* Renderer::GetTarget(){return SDL_GetRenderTarget(render);}

MenuItem* MenuList::Add(MenuItem* item){
    children->push_back(item);
    int m=0;
    for(auto& item:*children){
        int w;
        SDL_QueryTexture(item->_t,NULL,NULL,&w,NULL);
        m=max(m,w);
    }
    _w=m+30;
    return item;
}
//越界抛out_of_range 返回对象需手动析构
MenuItem* MenuList::Remove(size_t index){
    if(index<children->size()){
        auto temp=children->operator[](index);
        children->erase(children->begin()+index);
        return temp;
    }
    throw out_of_range(to_string(index));
}
//添加请使用xxx->Add(项),不要直接操作children,否则会导致可能的显示问题
MenuList::MenuList(int level,vector<MenuItem*>* children){
    this->level=level;
    this->children=children;
    _on=-1;
    _cur=0;
    int m=0;
    for(auto item:*children){
        int w;
        SDL_QueryTexture(item->_t,NULL,NULL,&w,NULL);
        m=max(m,w);
    }
    _w=m+30;
}
MenuList::~MenuList(){
    for(auto& item:*children)delete item;
    delete children;
}

bool MenuItem::DrawCheck(){return false;}
MenuItem::MenuItem(Window* window,MenuList* l,bool enabled,bool value,
void(*func)(MenuItem*)){
    _r=window->renderer;
    _l=l;
    this->enabled=enabled;
    this->func=func;
    _v=value;
}

MenuItem::~MenuItem(){
    if(_t){
        SDL_DestroyTexture(_t);
    }
    if(_l){
        delete _l;
    }
}

MenuText::MenuText(Window* window,const char* text,bool enabled,
bool value,void(*func)(MenuItem*),Font* font,SDL_Color color,MenuList* l):
MenuItem(window,l,enabled,value,func){
    _text=text;
    _font=font;
    _color=color;
    _t=_r->RenderText(font,text,color);
    SDL_QueryTexture(_t,NULL,NULL,&_w,NULL);
}
void MenuText::SetText(const char* new_text,Font* font,SDL_Color color){
    _text=new_text;
    _font=font;
    _color=color;
    _t=_r->RenderText(font,new_text,color);
    SDL_QueryTexture(_t,NULL,NULL,&_w,NULL);
}
bool MenuText::DrawCheck(){return true;}
const char* MenuText::text(){return _text;}
SDL_Color MenuText::color(){return _color;}
Font* MenuText::font(){return _font;}
MenuText::~MenuText(){}


void Renderer::DrawTexture(SDL_Rect* src,SDL_Rect* dst,SDL_Texture* texture,
    double angle,SDL_RendererFlip flip){
        SDL_RenderCopyEx(render,texture,src,dst,angle,0,flip);
}
Renderer::Renderer(SDL_Window* window,SDL_BlendMode mode){
    render=SDL_CreateRenderer(window,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_TARGETTEXTURE);
    gRender=render;
    SDL_SetRenderDrawBlendMode(render,mode);
}
Renderer::~Renderer(){
    SDL_DestroyRenderer(render);
}
void Renderer::Update(){
    SDL_RenderPresent(render);
}
void Renderer::SetColor(SDL_Color color){
    SDL_SetRenderDrawColor(render,color.r,color.g,color.b,color.a);
}
void Renderer::DrawLine(SDL_Point& p1,SDL_Point& p2){
    SDL_RenderDrawLine(render,p1.x,p1.y,p2.x,p2.y);
}
void Renderer::DrawRect(SDL_Rect& rect){
    SDL_RenderDrawRect(render,&rect);
}
void Renderer::DrawPoint(SDL_Point p){
    SDL_RenderDrawPoint(render,p.x,p.y);
}
void Renderer::FillRect(SDL_Rect& rect){
    SDL_RenderFillRect(render,&rect);
}
void Renderer::DrawTexture(SDL_Rect* src,SDL_Rect* dst,SDL_Texture* texture){
    SDL_RenderCopy(render,texture,src,dst);
}
//target==NULL->窗口
void Renderer::SetTarget(SDL_Texture* target){
    SDL_SetRenderTarget(render,target);
}
void Renderer::Clear(){
    SDL_RenderClear(render);
}
//将指定字符串按U8单位分割后缓存，返回元数据
vector<U8Unit>* Renderer::SaveU8Units(const char* text,Font* font,SDL_Color color){
    auto temp=new vector<U8Unit>();
    auto r=render;
    for(;;){
        auto len=GetU8Len(text);
        int c=0;
        memcpy(&c,text,len);
        auto t=font->RenderUnit(r,c,color);
        int w;
        SDL_QueryTexture(t,NULL,NULL,&w,NULL);
        temp->push_back({w,len});
        text+=len;
        if(!*text){
            return temp;
        }
    }
}
//清空并渲染区间内的所有u8单元，宽到了就停 返回渲染停止时的位置
const char* Renderer::RenderHorizontalText(SDL_Texture* _t,SDL_Color background,Font* font,const char* begin,const char* end,SDL_Color color){
    auto render=this->render;
    int max_w;
    SDL_Rect r{0,0};//w h由渲染结果决定
    SDL_QueryTexture(_t,NULL,NULL,&max_w,NULL);
    SDL_SetRenderTarget(render,_t);
    SDL_SetRenderDrawColor(render,background.r,background.g,background.b,background.a);
    SDL_RenderClear(render);
    while(begin!=end){
        auto len=GetU8Len(begin);
        int temp=0;
        memcpy(&temp,begin,len);
        auto t=font->RenderUnit(render,temp,color);
        SDL_QueryTexture(t,NULL,NULL,&r.w,&r.h);
        SDL_RenderCopy(render,t,NULL,&r);
        r.x+=r.w;
        if(r.x>=max_w){
            SDL_SetRenderTarget(render,NULL);
            return begin;
        }
        begin+=len;
    }
    SDL_SetRenderTarget(render,NULL);
    return begin;
}
void Renderer::SetBlendMode(SDL_BlendMode mode){
    SDL_SetRenderDrawBlendMode(render,mode);
}
const char* Renderer::RenderHighlightHorizontalText(SDL_Texture* _t,SDL_Color background,Font* font,const char* begin,
const char* end,SDL_Color text_color,
const char* h_begin,const char* h_end,SDL_Color high_color){
    auto render=this->render;
    int max_w;
    SDL_Rect r{0,0};//w h由渲染结果决定
    SDL_Rect highlight{0,0,0,font->height()};//w由渲染结果决定
    SDL_QueryTexture(_t,NULL,NULL,&max_w,NULL);
    SDL_SetRenderTarget(render,_t);
    SDL_SetRenderDrawColor(render,background.r,background.g,background.b,background.a);
    SDL_RenderClear(render);
    SDL_SetRenderDrawColor(render,high_color.r,high_color.g,high_color.b,high_color.a);
    while(begin!=end){
        auto len=GetU8Len(begin);
        int temp=0;
        memcpy(&temp,begin,len);
        auto t=font->RenderUnit(render,temp,text_color);
        SDL_QueryTexture(t,NULL,NULL,&r.w,&r.h);
        if(h_begin<=begin&&begin<h_end){
            highlight.x=r.x,highlight.w=r.w;
            SDL_RenderFillRect(render,&highlight);
        }
        SDL_RenderCopy(render,t,NULL,&r);
        r.x+=r.w;
        if(r.x>=max_w){
            SDL_SetRenderTarget(render,NULL);
            return begin;
        }
        begin+=len;
    }
    SDL_SetRenderTarget(render,NULL);
    return begin;
}
SDL_Texture* BuildText(SDL_Renderer* render,TTF_Font* font,const char* text,SDL_Color color){
    auto s=TTF_RenderUTF8_Blended(font,text,color);
    auto temp=SDL_CreateTextureFromSurface(render,s);
    SDL_FreeSurface(s);
    return temp;
}
SDL_Texture* Renderer::RenderText(Font* font,const char* text,SDL_Color color){
    return BuildText(render,font->_font,text,color);
}
SDL_Texture* Renderer::CreateTexture(int w,int h,Uint32 format,int access){
    return SDL_CreateTexture(render,format,access,w,h);
}
//找不到返回NULL
SDL_Texture* Renderer::LoadTexture(const char* path){
    return IMG_LoadTexture(render,path);
}
//将一个u8单元传入后返回单个渲染字 不需要析构(在字体内部已缓存记录)
SDL_Texture* Renderer::RenderSingleText(Font* font,int u8_value,SDL_Color color){
    return font->RenderUnit(render,u8_value,color);
}

const int Font::size(){return _size;}
const int Font::height(){return TTF_FontHeight(_font);}

SDL_Texture* Font::RenderUnit(SDL_Renderer* renderer,int u8_value,SDL_Color color){
    int v=*(int*)&color;
    char buffer[8];
    buffer[4]=0;
    *(int*)buffer=u8_value;
    auto f=_cache->find(v);
    if(f==_cache->end()){
        f=_cache->insert(make_pair(v,new unordered_map<int,SDL_Texture*>())).first;
    }
    auto m=f->second;
    auto g=m->find(u8_value);
    if(g==m->end()){
        return m->insert(make_pair(u8_value,BuildText(renderer,_font,buffer,color))).first->second;
    }
    return g->second;
}
//清理缓存
void Font::Clear(){
    for(auto item=_cache->begin(),end=_cache->end();item!=end;item++){
        auto m=item->second;
        for(auto i=m->begin(),e=m->end();i!=e;i++){
            SDL_DestroyTexture(i->second);
        }
        delete m;
    }
    delete _cache;
    _cache=new unordered_map<int,unordered_map<int,SDL_Texture*>*>();
}

//如果字体不存在，抛出FileNotFoundException
Font::Font(const char* path,int init_size){
    _cache=new unordered_map<int,unordered_map<int,SDL_Texture*>*>();
    _font=TTF_OpenFont(path,init_size);
    if(!_font){
        throw FileNotFoundException();
    }
    _size=init_size;
}
Font::~Font(){
    for(auto item=_cache->begin(),end=_cache->end();item!=end;item++){
        auto m=item->second;
        for(auto i=m->begin(),e=m->end();i!=e;i++){
            SDL_DestroyTexture(i->second);
        }
        delete m;
    }
    delete _cache;
    TTF_CloseFont(_font);
}

Font* default_font;//思源宋体

unsigned int GetU8Len(const char* text){
    char onget=text[0];
    if(!((onget>>7)&1)){
        return 1;
    }
    if(((onget>>6)&1)&&((onget>>7)&1)){
        if(((onget>>5)&1)){
            if(((onget>>4)&1)){
                return 4;
            }
            else{
                return 3;
            }
        }
        else{
            return 2;
        }
    }
    return 1;
}

SDL_Cursor *edit_cursor,*arrow_cursor,*button_cursor;

Uint32 frame_delay=1000/30;

Control::Control(Window* window){
    _belong=window;
    _enabled=true;
    _visible=true;
    resizable=true;
    draggable=true;
    pressed=false;
    layer=0;
}

const Window* Control::window(){return _belong;}
bool Control::enable(){return _enabled;}
SDL_Rect& Control::area(){return _area;}

void Control::SetEnable(bool v){_enabled=v;}
void Control::SetPosition(int x,int y){
    _area.x=x,_area.y=y;
}
void Control::OffsetPosition(int x,int y){
    _area.x+=x,_area.y+=y;
}
void Control::SetSize(int w,int h){if(!resizable)return;_area.w=w,_area.h=h;}
void Control::SetVisible(bool v){_visible=v;}

void Control::_draw(){}
void Control::_draw_edge(){
    _belong->renderer->SetColor({255,255,0,255});
    _belong->renderer->DrawRect(_area);
}
void Control::_mouse_move(int x,int y){}
void Control::_press(int x,int y,int clicks,unsigned char key){}
void Control::_release(int x,int y,int clicks,unsigned char key){}
void Control::_mouse_wheel(SDL_MouseWheelEvent& event){}
void Control::_key_down(SDL_Keysym& sym){}
void Control::_key_up(SDL_Keysym& sym){}
void Control::_input(const char* text){}
void Control::_drag(int x,int y){
    if(draggable){
        OffsetPosition(x,y);
    }
}
void Control::OnFocus(){}
void Control::LoseFocus(){}

Control::~Control(){}

bool Window::_mouse_move(int x,int y){return true;}
bool Window::_mouse_release(int x,int y,int clicks,unsigned char key){return true;}
bool Window::_mouse_press(int x,int y,int clicks,unsigned char key){return true;}
bool Window::_key_up(SDL_Keysym& sym){return true;}
bool Window::_key_down(SDL_Keysym& sym){return true;}
void Window::_draw(){}
unsigned int Window::id(){return _id;}

bool Window::_close(){return true;}
Control* Window::AddControl(Control* c){
    controls.push_back(c);
    return c;
}

bool Control::visible(){return _visible;}

void Window::Draw(){
    auto renderer=this->renderer;
    renderer->SetTarget(NULL);
    renderer->SetColor(background);
    renderer->Clear();
    for(auto& item:controls){
        if(item->visible())item->_draw();
    }
    for(auto& item:focus){
        if(item->visible())item->_draw_edge();
    }
    _draw();
    renderer->Update();
    for(auto item=tasks.begin(),end=tasks.end();item!=end;){
        auto cur=item;
        item++;
        auto t=*cur;
        if(++t->cur>=t->max){
            t->func(t->arg);
            if(tasks.find(t)==tasks.end()){
                continue;
            }
            if(t->forever){
                t->cur=0;
                continue;
            }
            t->finish(t->finish_arg);
            delete t;
            tasks.erase(cur);
            continue;
        }
        if(t->every){
            t->func(t->arg);
        }
    }
}